#include <drogon/drogon.h>
#include "ProjectTimer.h"
#include "OCMain/FileUtil.h"
#include "OCMain/Date.h"
#include "OCMain/Log.h"
#include "OCNetCDF/NetcdfExtract.h"
#include "OCVolume/VolumeD3Writer.h"

template<> ProjectTimer* CSingleton<ProjectTimer>::msSingleton = 0;

const String dbName = "MicroWind3d";
#define maxLevelIndex 53

ProjectTimer::ProjectTimer()
{
}

ProjectTimer::~ProjectTimer()
{

}
inline bool removeDir(String dir)
{
	if (StringUtil::startsWith(dir, ".")) return false;

	StringVector files = FileUtil::findFiles(dir);

	for (auto file : files)
	{
		if (file == "." || file == "..") continue;
		String fileName = joinPath(dir, file);
		if (FileUtil::typeOfFile(fileName) == FileUtil::REGULAR_FILE)
		{
			FileUtil::deleteFile(fileName);
		}
		else
		{
			removeDir(fileName);
		}
	}

	return FileUtil::deletFolder(dir);
}

inline void remove(String dateTime)
{
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			dbClient->execSqlSync(
				"DELETE FROM datetime WHERE timestamp < $1"
				, dateTime
			);
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}
}

inline String formatDbDateStr(String dateTime)
{
	String formatDateTime = dateTime.substr(0, 4) + "-" + dateTime.substr(4, 2) + "-"
		+ dateTime.substr(6, 2) + " " + dateTime.substr(8, 2)
		+ ":" + dateTime.substr(10, 2)
		+ ":00";
	return formatDateTime;
}

inline String formatFileDateStr(Date date)
{
	return date.toCustomedFormattedStringLocal("%Y%m%d", false);
}

inline String isMatchFormat(const String& str) {
	const std::regex pattern(R"(Z_NAFP_C_BABJ_\d{14}_P_3DWind_RT_BEPKTAM_5m_5MIN-MUL-(\d{12})\.nc)");
	std::smatch match;
	regex_match(str, match, pattern);
	if (match.size() == 2)
	{
		return match[1].str();
	}
	return "";
}

inline StringVector findFiles(String path)
{
	StringVector sv;
	auto files = FileUtil::findFiles(path);
	for (const auto& name : files)
	{
		String dateTime = isMatchFormat(name);
		if (!dateTime.empty())
		{
			sv.push_back(name);
		}
	}
	return sv;
}

inline StringVector findCaches(String path)
{
	StringVector sv;
	auto files = FileUtil::findFiles(path);
	for (const auto& name : files)
	{
		if (name.size() == 8)
		{
			sv.push_back(name);
		}
	}
	return sv;
}

String ProjectTimer::getFile(String dateTime, String region)
{
	if (dateTime.length() != 19) return "";
	String datePath = Date::fromDbStringLocal(dateTime).toCustomedFormattedStringLocal("%Y%m%d%H%M", false);
	String fileName = joinPath(_outputPath, datePath.substr(0, 8), datePath + ".bin.gz");
	return FileUtil::getFileBinary(fileName);
}

void ProjectTimer::convert(String dateFolder,String name)
{
	String customOutputName = isMatchFormat(name);
	String output = joinPath(_outputPath, customOutputName.substr(0,8));
	String outputFileName = joinPath(output, customOutputName) + ".bin.gz";
	if (FileUtil::existFile(outputFileName))
	{
		return;
	}
	String fileName = joinPath(_dataPath, dateFolder, name);

	VolumeOptions options;
	options.knownNotLonLat = true;
	options.knownMaxLevelIndex = maxLevelIndex;
	options.customScaler = 0.01f;
	options.preferHalfFloat = false;
	options.setWriter(new VolumeD3Writer());
	options.getWriter().setMakeDirForFile(true);
	options.getWriter().setOutputPath(output);
	options.getWriter().setGZipCompress(true);
	options.getWriter().customOutputName = customOutputName;

	GridDataCompute gridDataCompute;
	gridDataCompute.add({ fileName, "UWIND" });
	gridDataCompute.add({ fileName, "VWIND" });
	gridDataCompute.add({ fileName, "WWIND" });
	NetcdfExtract::computeAndExtract3D(gridDataCompute, options);

	if(FileUtil::existFile(outputFileName))
	{
		insert(formatDbDateStr(customOutputName));
	}
}

void ProjectTimer::doJob()
{
	String dataPath = _dataPath;
	//Date date = Date::now().after(-Date::timezoneOffset()).roundSecond();
	Date date = Date::now().roundSecond();

	String dateFolder = formatFileDateStr(date);
	String dateFolder2 = joinPath(dateFolder.substr(0, 4), dateFolder);
	OCPP_LOG_INFO << "Work time:" << date.toCustomedFormattedStringLocal("%Y-%m-%d %H:%M", false) << "\n";
	auto ncFiles = findFiles(joinPath(dataPath, dateFolder2));
	for (const auto& name : ncFiles)
	{
		convert(dateFolder2,name);
	}
}

void ProjectTimer::expired()
{
	Date date = Date::now().after(-Date::timezoneOffset()).roundSecond()
		.after(-this->_expiredTime * 3600 * 24);

	String dateFolder = formatFileDateStr(date);

	remove(formatDbDateStr(dateFolder + "000000"));

	int expiredDateTime = std::stoi(dateFolder);
	StringVector folders = findCaches(this->_outputPath);
	for (const auto& f : folders)
	{
		int intDateTime = std::stoi(f);
		if (intDateTime < expiredDateTime)
		{
			removeDir(joinPath(this->_outputPath,f));
		}
	}
}

std::set<String> ProjectTimer::query(String startTime, String endTime)
{
	std::set<String> res;

	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			const drogon::orm::Result result = dbClient->execSqlSync(
				"select * from datetime where timestamp>=$1 and timestamp<=$2"
				, startTime
				, endTime
			);
			for (auto row : result)
			{
				res.insert(row["timestamp"].as<std::string>());
			}
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}

	return res;
}


int ProjectTimer::insert(String dateTime)
{
	drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
	if (dbClient)
	{
		try {
			const drogon::orm::Result result = dbClient->execSqlSync(
				"INSERT INTO datetime (timestamp) VALUES($1)"
				, dateTime
			);
			return result.affectedRows();
		}
		catch (drogon::orm::DrogonDbException& ex) {
			LOG_ERROR << ex.base().what() << "\n";
		}
		catch (...) {
			LOG_ERROR << "unknow error.\n";
		}
	}
	return 0;
}

void ProjectTimer::start()
{
	if (!FileUtil::existFile(_outputPath))
	{
		FileUtil::makeDirectory(_outputPath);
	}

	String fileName = joinPath(_outputPath, dbName + ".db");
	drogon::app().createDbClient("sqlite3", "", 0, "", "", "", 1, A2U8(fileName), dbName);
	
	static std::thread t;
	drogon::app().getLoop()->runAfter(0, [this] {
		drogon::orm::DbClientPtr dbClient = drogon::app().getDbClient(dbName);
		if (dbClient)
		{
			try {
				String sql1 = "CREATE TABLE IF NOT EXISTS datetime(\
				id INTEGER PRIMARY KEY,\
				timestamp TIMESTAMP DEFAULT(DATETIME('now', 'localtime')),\
				UNIQUE (timestamp))";
				dbClient->execSqlSync(sql1);
			}
			catch (drogon::orm::DrogonDbException& ex) {
				LOG_ERROR << ex.base().what() << "\n";
			}
			catch (...) {
				LOG_ERROR << "unknow error.\n";
				return;
			}
		}

		t = std::thread([this]() {
			int interval = 600;
			uint32 count = 0;
			while (true)
			{
				count += interval;
#ifdef _DEBUG0
				this->_expiredTime = -1;
				if (count >= 3600 * 24 || true)
#else
				if (count >= 3600 * 24)
#endif
				{
					this->expired();
					count = 0;
				}
				this->doJob();
				std::this_thread::sleep_for(std::chrono::seconds(3 * 60));
			}
			});
		t.detach();
	});
}

